package ru.mirent;

import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import org.mockito.MockedConstruction;
import org.mockito.stubbing.Answer;

import java.util.List;

import static org.mockito.Mockito.mockConstruction;
import static org.mockito.Mockito.when;

/**
 * Создение заглушки для последующего внутреннего вызова в другом классе
 */
public class MockConstructionTest {

    /**
     * Создание заглушки экземпляра класса для внутреннего вызова в другом класса без явного указания заглушки
     */
    @Test
    void mockingConstructor() {
        // Создание заглушки класса Cat для вызова в другом классе Sound
        try (MockedConstruction<Cat> mock = mockConstruction(Cat.class, new MockedConstruction.MockInitializer() {
            @Override
            public void prepare(Object mock, MockedConstruction.Context context) throws Throwable {
                // Получение параметра конструктора объекта Cat
                List<?> args = context.arguments();
                String param = args.get(1).toString();
                // Проверка значения параметра
                Assertions.assertEquals("CALL_OBJECT", param);

                // Настройка данных для возврата в заглушке
                when(((Cat) mock).makeSound())
                        // Возврат замоканного значения метода makeSound()
                        .thenAnswer((Answer<String>) invocation -> {
                            System.out.println("Вызван метод заглушки makeSound() с результатом 'MOCKED_OBJECT'");
                            return "MOCKED_OBJECT";
                        });
            }
        })) {

            // Создание базового объекта Sound, в котором будет создан целевой замоканый объект Cat
            Sound sound = new Sound("CALL_OBJECT");
            Assertions.assertEquals("MOCKED_OBJECT", sound.play());

            // Получение количества созданных экземпляров целевого класса
            List<Cat> constructed = mock.constructed();
            // Проверка количества созданных объектов
            Assertions.assertEquals(1, constructed.size());
        }

        // Вызов класса Sound с реальным незамоканным объектом Cat
        Assertions.assertEquals("REAL_OBJECT", new Sound("REAL_OBJECT").play());
    }

    /**
     * Класс демонстрации работы создания заглушки для класса, используемого внутри другого класса
     */
    class Cat {
        private String name;

        public Cat() {
            this("DEFAULT_NAME");
            System.out.println("Был вызван реальный дефолтный конструктор объекта Cat");
        }

        public Cat(String name) {
            System.out.println("Вызван реальный конструктор объекта Cat с параметром: " + name);
            this.name = name;
        }

        public String makeSound() {
            System.out.println("Вызван реальный метод makeSound() реального объекта: " + name);
            return name;
        }
    }

    /**
     * Базовый объект, в котором создается и вызывается метод замоканного объекта
     */
    class Sound {
        private Cat cat;

        Sound(String name) {
            System.out.println("Вызван реальный конструктор объекта Sound с параметром: " + name);
            this.cat = new Cat(name);
        }

        String play() {
            System.out.println("Вызван реальный метод play() объекта Sound");
            return cat.makeSound();
        }
    }
}
